/* 
实现此蓝图
ajax({
    // method: "GET",
    url: "http://httpbin.org/get",
    data: { username: "admin", password: "123456" },
    onSuccess: (data) => box.innerText = `data=${data}`,
    onFail: err => box.innerText = `err=${JSON.stringify(err)}`
});
*/

/* 1.0 GET版 */
function ajax1(config) {
    // 创建XHR对象
    const xhr = new XMLHttpRequest();

    // 解构（structure de-structure）出各种用户配置
    let { method, url, onSuccess, onFail, data } = config;

    // 提取出用户传入的method 如果没有传 则默认使用GET
    method = method || "GET";

    /* 处理URL */
    /* GET请求时 将数据拼接为查询参数（searchParams/queryString） username=admin&password=123456 */
    if (method === "GET" && data) {
        // 将data对象转化为查询参数
        // 提前准备好查询参数串
        let searchParamsStr = "";
        // 遍历key-value
        for (let key in data) {
            // 拼接字符串 key1=value1&key2=value2
            searchParamsStr += `&${key}=${data[key]}`;
        }

        // 锯掉头部的& &key1=value1&key2=value2
        // 从1截取到末尾
        searchParamsStr = searchParamsStr.slice(1);

        // 查询参数拼接到url之后
        url += `?${searchParamsStr}`;
        console.log("url=", url);
    }

    // 打开通向目标地址的管道
    xhr.open(method, url);

    /* 配置回调函数 */
    xhr.onload = function () {
        // 回调用户配置的[成功回调] 入参网络数据
        onSuccess(xhr.responseText);
    };
    xhr.onerror = function (err) {
        // 回调用户配置的[失败回调] 入参错误信息
        onFail(err);
    };

    // 发送请求
    xhr.send(null);
}

/**
 * 将data对象转化为查询参数串 key1=value1&key2=value2
 * @param {Object} data
 * @returns 查询参数串
 */
function dataToSearchParamStr(data) {
    // 将data对象转化为查询参数
    // 提前准备好查询参数串
    let searchParamsStr = "";
    // 遍历key-value
    for (let key in data) {
        // 拼接字符串 key1=value1&key2=value2
        searchParamsStr += `&${key}=${data[key]}`;
    }

    // 锯掉头部的& &key1=value1&key2=value2
    // 从1截取到末尾
    searchParamsStr = searchParamsStr.slice(1);

    // 返回重构好的查询参数串
    return searchParamsStr;
}

/* 2.0 GET版+表单版+JSON版 */
function ajax(config) {
    // 创建XHR对象
    const xhr = new XMLHttpRequest();

    // 解构（structure de-structure）出各种用户配置
    let { method, url, onSuccess, onFail, data, dataType } = config;

    // 提取出用户传入的method 如果没有传 则默认使用GET
    method = method || "GET";

    // 只要POST请求 则请求体默认JSON
    ["POST", "PUT", "PATCH"].includes(method) &&
        !dataType &&
        (dataType = "json");

    // 准备好一个请求体
    let reqBody = null;

    /* 处理URL */
    /* GET请求时 将数据拼接为查询参数（searchParams/queryString） username=admin&password=123456 */
    if (method === "GET" && data) {
        // 查询参数拼接到url之后
        url += `?${dataToSearchParamStr(data)}`;
        console.log("url=", url);
    }

    // POST一个表单时
    else if (method === "POST" && dataType === "form") {
        // 请求体变成 username=admin&password=123456
        reqBody = dataToSearchParamStr(data);
    }

    // POST/PUT一个JSON
    else if (["POST", "PUT", "PATCH"].includes(method) && dataType === "json") {
        reqBody = JSON.stringify(data);
    }

    // 打开通向目标地址的管道
    xhr.open(method, url);

    /* 根据dataType设置请求头 */
    switch (true) {
        case dataType === "form":
            xhr.setRequestHeader(
                "Content-Type",
                "application/x-www-form-urlencoded"
            );
            break;
        case dataType === "json":
            xhr.setRequestHeader("Content-Type", "application/json");
            break;
        default:
            break;
    }

    /* 配置回调函数 */
    xhr.onload = function () {
        // 回调用户配置的[成功回调] 入参网络数据
        onSuccess(xhr.responseText);
    };
    xhr.onerror = function (err) {
        // 回调用户配置的[失败回调] 入参错误信息
        onFail(err);
    };

    // 发送请求 携带请求体
    xhr.send(reqBody);
}

/* 3.0 GET版+表单版+JSON版+处理异常 */
function ajax(config) {
    // 创建XHR对象
    const xhr = new XMLHttpRequest();

    // 解构（structure de-structure）出各种用户配置
    let { method, url, onSuccess, onFail, data, dataType, headers } = config;

    /* 如果用户是个小可爱 连URL都不给 [教育]它一下 */
    if (!url) {
        // 抛出错误（代码惨死于当前行）
        throw new Error("亲爱的萌萌哒:URL必须给哦亲");

        console.log(
            "执行到此处也可以到前台领取女朋友一个+现金100万+月薪30K offer一个"
        );
    }

    /* 如果用户没有给成功失败回调 帮用户给它一个默认值 */
    !onSuccess && (onSuccess = (data) => console.log("data=", data));
    !onFail && (onFail = (err) => console.error("err=", err));

    // 提取出用户传入的method 如果没有传 则默认使用GET
    method = method || "GET";

    // 只要POST请求 则请求体默认JSON
    ["POST", "PUT", "PATCH"].includes(method) &&
        !dataType &&
        (dataType = "json");

    // 准备好一个请求体
    let reqBody = null;

    /* 处理URL */
    /* GET请求时 将数据拼接为查询参数（searchParams/queryString） username=admin&password=123456 */
    if (method === "GET" && data) {
        // 查询参数拼接到url之后
        url += `?${dataToSearchParamStr(data)}`;
        // console.log("url=", url);
    }

    // POST一个表单时
    else if (method === "POST" && dataType === "form") {
        // 请求体变成 username=admin&password=123456
        reqBody = dataToSearchParamStr(data);
    }

    // POST/PUT一个JSON
    else if (["POST", "PUT", "PATCH"].includes(method) && dataType === "json") {
        reqBody = JSON.stringify(data);
    }

    // 打开通向目标地址的管道
    xhr.open(method, url);

    /* 根据dataType设置请求头 */
    switch (true) {
        case dataType === "form":
            xhr.setRequestHeader(
                "Content-Type",
                "application/x-www-form-urlencoded"
            );
            break;
        case dataType === "json":
            xhr.setRequestHeader("Content-Type", "application/json");
            break;
        default:
            break;
    }

    /* 设置其它请求头 */
    if (headers) {
        for (let name in headers) {
            xhr.setRequestHeader(name, headers[name]);
        }
    }

    /* 配置回调函数 */
    xhr.onload = function () {
        // 回调用户配置的[成功回调] 入参网络数据
        onSuccess(xhr.responseText);
    };
    xhr.onerror = function (err) {
        // 回调用户配置的[失败回调] 入参错误信息
        onFail(err);
    };

    // 发送请求 携带请求体
    xhr.send(reqBody);
}

/* 4.0 既能执行AJAX请求 又能返回Promise对象 */
function ajaxPromise(config) {
    // 返回一个执行AJAX的Promise对象
    return new Promise((resolve, reject) => {
        // ajax(config)
        ajax({
            ...config,

            // 请求成功时履约，就近回调then中的操作
            onSuccess: (data) => resolve(data),

            // 请求失败时毁约，就近回调catch中的操作
            onFail: (err) => reject(err),
        });
    });
}
